﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using CoolCode;
using CoolCode.Linq;
using CoolCode.Linq.Expressions;
using CoolCode.Data;
using CoolCode.Services;
using CoolCode.Search;
using CoolCode.Web.Syndication;
using XNoteBook.Entities;
using XNoteBook.Models;

namespace XNoteBook.Services.Implement
{
	public class PostService : ServiceBase, IPostService
	{
		#region Data Access Objects

		private IRepository<Post> _PostDao;
		public IRepository<Post> PostDao
		{
			get
			{
				if (_PostDao == null)
				{
					_PostDao = this.CreateRepository<Post>();
				}
				return _PostDao;
			}
			set
			{
				_PostDao = value;
			}
		}

		private IRepository<TreeCategory> _TreeCategoryDao;
		public IRepository<TreeCategory> TreeCategoryDao
		{
			get
			{
				if (_TreeCategoryDao == null)
				{
					_TreeCategoryDao = this.CreateRepository<TreeCategory>();
				}
				return _TreeCategoryDao;
			}
			set
			{
				_TreeCategoryDao = value;
			}
		}

		private IRepository<Category> _CategoryDao;
		public IRepository<Category> CategoryDao
		{
			get
			{
				if (_CategoryDao == null)
				{
					_CategoryDao = this.CreateRepository<Category>();
				}
				return _CategoryDao;
			}
			set
			{
				_CategoryDao = value;
			}
		}

		private IRepository<PostViewHistory> _PostViewHistoryDao;
		public IRepository<PostViewHistory> PostViewHistoryDao
		{
			get
			{
				if (_PostViewHistoryDao == null)
				{
					_PostViewHistoryDao = this.CreateRepository<PostViewHistory>();
				}
				return _PostViewHistoryDao;
			}
			set
			{
				_PostViewHistoryDao = value;
			}
		}

		private IRepository<PostOuterInfo> _PostOuterInfoDao;
		public IRepository<PostOuterInfo> PostOuterInfoDao
		{
			get
			{
				if (_PostOuterInfoDao == null)
				{
					_PostOuterInfoDao = this.CreateRepository<PostOuterInfo>();
				}
				return _PostOuterInfoDao;
			}
			set
			{
				_PostOuterInfoDao = value;
			}
		}

		private IRepository<PostOuterLink> _PostOuterLinkDao;
		public IRepository<PostOuterLink> PostOuterLinkDao
		{
			get
			{
				if (_PostOuterLinkDao == null)
				{
					_PostOuterLinkDao = this.CreateRepository<PostOuterLink>();
				}
				return _PostOuterLinkDao;
			}
			set
			{
				_PostOuterLinkDao = value;
			}
		}

		#endregion

		#region Post

		public void SavePost(Post entity, string parentID)
		{
			var e = GetPost(entity.PostID);
			if (e != null)
			{
				this.PostDao.Delete(e);
			}
			else
			{
				this.TreeCategoryDao.Insert(new TreeCategory
				{
					NodeID = entity.PostID,
					ParentID = parentID,
					SortIndex = GetMaxSortIndex(parentID),
					Type = (int)NodeType.Post
				});
			}
			this.PostDao.Insert(entity);
			this.Submit();
		}

		public void SavePost(Post entity)
		{
			var e = GetPost(entity.PostID);
			if (e != null)
			{
				this.PostDao.Delete(e);
				entity.CreateTime = e.CreateTime;
				entity.CreateUserID = e.CreateUserID;
				entity.CreateUserName = e.CreateUserName;
			}
			this.PostDao.Insert(entity);
			this.Submit();
		}

		public void UpdatePost(string id, Expression<Func<Post, Post>> update)
		{
			this.PostDao.Update(c => c.PostID == id, update);
			this.Submit();
		}

		public void DeletePost(string id)
		{
			this.PostDao.Delete(c => c.PostID == id);
			this.Submit();
		}

		public Post GetPost(string id)
		{
			return this.PostDao.Get(c => c.PostID == id);
		}

		public List<Post> ListPost()
		{
			return this.PostDao.List();
		}

		//public IPaginatedList<Post> PagingPost(PageParam p)
		//{
		//    return this.PostDao.Paging(p); 
		//}

		public IPageable<Post> PagingPost(PageParam p)
		{
			return this.PostDao.OrderBy(c => c.Title).AsPageable(p);
		}

		public IQueryable<Post> QueryPost()
		{
			return this.PostDao.OrderBy(c => c.Title);
		}

		public void ViewPost(string id, string user)
		{
			var entity = GetPost(id);
			if (entity != null)
			{
				entity.ViewNum += 1;
				this.PostViewHistoryDao.Insert(new PostViewHistory
				{
					PostID = id,
					Title = entity.Title,
					ReaderID = user,
					ReadTime = DateTime.Now
				});
				this.Submit();
			}
		}

		#endregion

		#region Category

		public void SaveCategory(Category entity, string parentID, int position)
		{
			var e = GetCategory(entity.CategoryID);
			if (e != null)
			{
				this.CategoryDao.Delete(e);
			}
			else
			{
				if (position > 0)
				{
					Expression<Func<TreeCategory, bool>> condition_update = c => c.ParentID == parentID && c.SortIndex > position;
					this.TreeCategoryDao.Update(condition_update, c =>
					{
						c.SortIndex += 1;
					});
				}

				this.TreeCategoryDao.Insert(new TreeCategory
				{
					NodeID = entity.CategoryID,
					ParentID = parentID,
					SortIndex = position,//GetMaxSortIndex(parentID) +1 ,
					Type = (int)NodeType.Category
				});
			}
			this.CategoryDao.Insert(entity);
			this.Submit();

		}

		public string MoveNode(TreeCategory entity, bool copy)
		{
			string id = entity.NodeID;
			Expression<Func<TreeCategory, bool>> condition_update = c => c.ParentID == entity.ParentID && c.SortIndex > entity.SortIndex;
			this.TreeCategoryDao.Update(condition_update, c =>
			{
				c.SortIndex -= 1;
			});

			this.TreeCategoryDao.Update(c => c.NodeID == entity.NodeID, c =>
			{
				c.ParentID = entity.ParentID;
				c.SortIndex = entity.SortIndex;
			});

			this.Submit();

			return id;
		}

		public void RenameCategory(Category entity)
		{
			this.CategoryDao.Update(c => c.CategoryID == entity.CategoryID, c => c.Title = entity.Title);
			this.Submit();
		}

		public Category GetCategory(string id)
		{
			return this.CategoryDao.Get(c => c.CategoryID == id);
		}

		public void DeleteCategory(string id)
		{
			this.CategoryDao.Delete(c => c.CategoryID == id);
			this.Submit();
		}

		public int GetMaxSortIndex(string parentID)
		{
			return this.TreeCategoryDao.Where(c => c.ParentID == parentID)//.Max(c => c.SortIndex)
				.OrderByDescending(c => c.SortIndex)
				.Select(c => c.SortIndex)
				.FirstOrDefault();
		}

		public IQueryable<NodeModel> QueryNode(IQueryBuilder<NodeModel> condition)
		{
			var query = (from t in TreeCategoryDao
						 join c in
							 (
								 (from cat in CategoryDao
								  select new NodeModel
								  {
									  Type = cat.CategoryID == "0" ? NodeType.Drive : NodeType.Category,
									  NodeID = cat.CategoryID,
									  Title = cat.Title,
									  CreateTime = cat.CreateTime,
									  CreateUserID = cat.CreateUserID,
									  CreateUserName = cat.CreateUserName,
									  UpdateTime = cat.UpdateTime,
									  UpdateUserID = cat.UpdateUserID,
									  UpdateUserName = cat.UpdateUserName,
									  Summary = string.Empty,
								  })
								 .Concat(
								 (from post in PostDao
								  select new NodeModel
								  {
									  Type = NodeType.Post,
									  NodeID = post.PostID,
									  Title = post.Title,
									  CreateTime = post.CreateTime,
									  CreateUserID = post.CreateUserID,
									  CreateUserName = post.CreateUserName,
									  UpdateTime = post.UpdateTime,
									  UpdateUserID = post.UpdateUserID,
									  UpdateUserName = post.UpdateUserName,
									  Summary = post.Summary,
								  }))
							 )
						 on t.NodeID equals c.NodeID
						 let s = from sub in TreeCategoryDao
								 where sub.ParentID == t.NodeID
								 select sub
						 orderby c.Type, c.Title
						 select new NodeModel
						 {
							 Type = c.Type,
							 NodeID = c.NodeID,
							 Title = c.Title,
							 CreateTime = c.CreateTime,
							 CreateUserID = c.CreateUserID,
							 CreateUserName = c.CreateUserName,
							 UpdateTime = c.UpdateTime,
							 UpdateUserID = c.UpdateUserID,
							 UpdateUserName = c.UpdateUserName,
							 Summary = c.Summary,
							 ParentID = t.ParentID,
							 SortIndex = t.SortIndex,
							 IsLeaf = (s.Count() == 0)
						 }
					 ).Where(condition.Expression);
			return query;
		}

		public void RenameNode(NodeModel entity)
		{
			switch (entity.Type)
			{
				case NodeType.Post:
					this.PostDao.Update(c => c.PostID == entity.NodeID, c => c.Title = entity.Title);
					break;
				case NodeType.Category:
					this.CategoryDao.Update(c => c.CategoryID == entity.NodeID, c => c.Title = entity.Title);
					break;
			}
			this.Submit();
		}

		public string SaveNode(NodeModel entity)
		{
			if (entity.SortIndex > 0)
			{
				Expression<Func<TreeCategory, bool>> condition_update = c => c.ParentID == entity.ParentID && c.SortIndex > entity.SortIndex;
				this.TreeCategoryDao.Update(condition_update, c =>
				{
					c.SortIndex += 1;
				});
			}
			else
			{
				entity.SortIndex = GetMaxSortIndex(entity.ParentID);
			}

			this.TreeCategoryDao.Insert(new TreeCategory
			{
				NodeID = entity.NodeID,
				ParentID = entity.ParentID,
				SortIndex = entity.SortIndex,
				Type = (int)(entity.Type == NodeType.Category ? NodeType.Category : NodeType.Post)
			});

			if (entity.Type == NodeType.Category)
			{
				this.CategoryDao.Insert(new Category
				{
					CategoryID = entity.NodeID,
					Title = entity.Title,
					CreateTime = DateTime.Now,
					CreateUserID = entity.CreateUserID,
					CreateUserName = entity.CreateUserName,
				});
			}
			else
			{
				this.PostDao.Insert(new Post
				{
					PostID = entity.NodeID,
					Title = entity.Title,
					CreateTime = DateTime.Now,
					CreateUserID = entity.CreateUserID,
					CreateUserName = entity.CreateUserName,
				});

			}
			this.Submit();
			return entity.NodeID;
		}

		public NodeListModel GetChildrenNodes(string parentID)
		{
			var query = QueryNode(QueryBuilder.Create<NodeModel>().Equals(c => c.ParentID, parentID));
			var list = query.ToList();

			return new NodeListModel
			{
				ParentID = parentID,
				Items = list
			};
		}

		public NodeListModel GetCategories(string parentID)
		{
			var query = QueryNode(QueryBuilder.Create<NodeModel>()
				.Equals(c => c.ParentID, parentID)
				.In(c => c.Type, NodeType.Category, NodeType.Drive));
			var list = query.ToList();

			return new NodeListModel
			{
				ParentID = parentID,
				Items = list
			};
		}

		public IEnumerable<NodeModel> GetSelfAndForefathers(string id)
		{
			Stack<NodeModel> container = new Stack<NodeModel>();
			getSelfAndForefathersHelp(id, container);
			return container;
		}

		private void getSelfAndForefathersHelp(string id, Stack<NodeModel> container)
		{
			var node = this.GetNode(id);

			if (node == null) return;

			container.Push(node);

			if (string.IsNullOrEmpty(node.ParentID)) return;

			getSelfAndForefathersHelp(node.ParentID, container);
		}

		public NodeModel GetNode(string id)
		{
			var query = from c in CategoryDao
						where c.CategoryID == id
						join t in TreeCategoryDao on c.CategoryID equals t.NodeID
						select new NodeModel
						{
							ParentID = t.ParentID,
							SortIndex = t.SortIndex,
							Type = c.CategoryID == "0" ? NodeType.Drive : NodeType.Category,
							NodeID = c.CategoryID,
							Title = c.Title,
							CreateTime = c.CreateTime,
							CreateUserID = c.CreateUserID,
							CreateUserName = c.CreateUserName,
							UpdateTime = c.UpdateTime,
							UpdateUserID = c.UpdateUserID,
							UpdateUserName = c.UpdateUserName,
						};

			return query.FirstOrDefault();
		}

		public void DeleteNode(string id)
		{
			((CoolCode.Data.Linq.Repository<TreeCategory>)this.TreeCategoryDao).Exec("EXEC DeleteNode {0}", id);
			/*
			return;

			var node = this.TreeCategoryDao.Get(c => c.NodeID == id);
			if (node == null) return;

			Expression<Func<TreeCategory, bool>> condition_update = c => c.ParentID == node.ParentID && c.SortIndex > node.SortIndex;

			this.TreeCategoryDao.Delete(c => c.NodeID == id);
			this.TreeCategoryDao.Update(condition_update, c =>
			{
				c.SortIndex -= 1;
			});

			switch ((CategoryType)node.Type)
			{
				case CategoryType.Post:
					this.DeletePost(id);
					break;
				case CategoryType.Category:
					this.DeleteCategory(id);
					break;
				default:
					break;
			}

			this.Submit();
			 * */
		}

		#endregion

		#region Search

		private readonly int PageSize_Search = 10;

		public IPaginatedList<SearchResult> Search(string q, int page, ILinqSearchEngine searchEngine, Func<Post, string> buildeUrl)
		{
			var query = searchEngine.Search(q, PostDao, new string[] { "Title", "Summary", "Text" });
			var result = query.OrderByDescending(c => c.UpdateTime ?? c.CreateTime);
			var searchResult = result.Paging(new PageParam(page, PageSize_Search)).Select(c =>
					new SearchResult
					{
						Title = c.Title,
						Content = c.Text,
						UpdateDate = c.UpdateTime ?? c.CreateTime,
						Url = buildeUrl(c),
						ResultType = SearchResultType.Blog
					});

			searchEngine.Highlighter.Highlight(q, searchResult);

			return searchResult;
		}

		#endregion

		#region Feed

		public FeedModel Rss()
		{
			var node = from t in TreeCategoryDao
					   join c in CategoryDao on t.ParentID equals c.CategoryID
					   select new
					   {
						   t.NodeID,
						   t.ParentID,
						   c.Title,
						   c.Description
					   };

			var queryPosts = (from p in PostDao
							  join n in node on p.PostID equals n.NodeID
							  let t = n
							  orderby p.CreateTime descending
							  select new FeedItem
							  {
								  ID = p.PostID,
								  UserName = p.CreateUserName,
								  PublishDate = p.CreateTime,
								  LastUpdatedTime = p.UpdateTime ?? p.CreateTime,
								  Title = p.Title,
								  Text = p.Html,
								  Email = p.CreateUserName,
								  Description = p.Summary,
								  Category = new FeedCategory
								  {
									  ID = t.ParentID,
									  Title = t.Title,
									  Description = t.Description
								  }
							  }
					   ).Take(10);
			var posts = queryPosts.ToList();
			var categories = posts.Select(c => c.Category).Distinct().ToList();

			FeedModel model = new FeedModel
			{
				UserName = "CoolCode",
				Email = "CoolCode",
				Title = "XNoteBook",
				Description = "XNoteBook",
				Text = "XNoteBook",
				Items = posts,
				Categories = categories
			};

			return model;
		}

		#endregion

		#region Top10

		public TopTenModel ListTop10PostsByFrequency()
		{
			TopTenModel model = new TopTenModel
			{
				Title = "最近浏览的随笔"
			};

			/*
SELECT * FROM dbo.Post p
INNER JOIN (
		SELECT pv.PostID, MAX(pv.ReadTime) AS ReadTime  FROM dbo.PostViewHistory pv 
		GROUP BY pv.PostID 
	) AS lastPV 
ON lastPV.PostID = p.PostID
ORDER BY lastPV.ReadTime DESC 
			 */
			var gv = from pv in PostViewHistoryDao
					 group pv by pv.PostID into gpv
					 select new
					 {
						 PostID = gpv.Key,
						 ReadTime = gpv.Max(c => c.ReadTime)
					 };
			var query = (from p in PostDao
						 join g in gv
						 on p.PostID equals g.PostID
						 orderby g.ReadTime descending
						 select new TopTenItem
						 {
							 ID = p.PostID,
							 Title = p.Title,
							 ViewNum = p.ViewNum,
							 EditDate = g.ReadTime
						 })
						 .Take(10);
			/*
			var query = (from p in PostDao
						 join pv in PostViewHistoryDao
						 on p.PostID equals pv.PostID into lastpv
						 let lastReadTime = lastpv.Count() > 0 ? (DateTime?)lastpv.Max(c => c.ReadTime) : null
						 where lastReadTime != null
						 orderby lastReadTime descending
						 select new TopTenItem
						 {
							 ID = p.PostID,
							 Title = p.Title,
							 ViewNum = p.ViewNum,
							 EditDate = lastReadTime.Value
						 })
			  			 .Take(10);
			*/
			model.AddRange(query);
			return model;
		}

		public TopTenModel ListTop10PostsByViews()
		{
			TopTenModel model = new TopTenModel
			{
				Title = "浏览最多的随笔"
			};

			var query = (from p in PostDao
						 select new TopTenItem
						 {
							 ID = p.PostID,
							 Title = p.Title,
							 ViewNum = p.ViewNum,
							 EditDate = p.UpdateTime ?? p.CreateTime
						 })
						 .OrderByDescending(c => c.ViewNum)
						 .Take(10);
			model.AddRange(query);
			return model;
		}

		public TopTenModel ListTop10PostsByDate()
		{
			TopTenModel model = new TopTenModel
			{
				Title = "最新的随笔"
			};

			var query = (from p in PostDao
						 select new TopTenItem
						 {
							 ID = p.PostID,
							 Title = p.Title,
							 ViewNum = p.ViewNum,
							 EditDate = p.UpdateTime ?? p.CreateTime
						 })
						 .OrderByDescending(c => c.EditDate)
						 .Take(10);
			model.AddRange(query);
			return model;
		}

		#endregion

		#region Import

		public void Import(PostOuterInfoModel model, string userID)
		{
			FeedReader reader = new FeedReader();
			FeedModel feedModel = reader.Read(model.OriginalUrl);

			int sortIndex = GetMaxSortIndex(model.CategoryID) + 1;

			string originalInfoID = Util.GenerateUID();

			this.PostOuterInfoDao.Insert(new PostOuterInfo
			{
				ID = originalInfoID,
				Description = model.Description,
				OriginalUrl = model.OriginalUrl,
				Remarks = model.Remarks,
				Status = model.Status,
				CategoryID = model.CategoryID,
				CreateTime = DateTime.Now,
				CreateUserID = userID,
				CreateUserName = userID
			});

			foreach (var item in feedModel.Items)
			{
				string id = Util.GenerateUID();
				this.TreeCategoryDao.Insert(new TreeCategory
				{
					NodeID = id,
					ParentID = model.CategoryID,
					SortIndex = sortIndex++,
					Type = (int)NodeType.Post
				});

				string text = item.Text.HtmlToText();

				this.PostDao.Insert(new Post
				{
					PostID = id,
					Title = item.Title.Cut(50),
					Html = item.Text,
					Text = text.Cut(1000),
					Summary = text.Cut(200),
					CreateTime = DateTime.Now,
					CreateUserID = userID,
					CreateUserName = userID,
				});

				this.PostOuterLinkDao.Insert(new PostOuterLink
				{
					PostID = id,
					OriginalUrl = item.Url,
					OriginalInfoID = originalInfoID
				});
			}

			this.Submit();
		}

		public IQueryable<PostOuterInfo> QueryPostOuterInfo(IQueryBuilder<PostOuterInfo> condition)
		{
			return this.PostOuterInfoDao.Where(condition.Expression).OrderBy(c => c.UpdateTime ?? c.CreateTime);
		}

		public PostOuterInfo GetPostOuterInfo(string id)
		{
			return this.PostOuterInfoDao.Get(c => c.ID == id);
		}

		public PostOuterInfoModel GetPostOuterInfoModel(string id)
		{
			var entity = this.GetPostOuterInfo(id);
			return entity == null ? null : new PostOuterInfoModel
			{
				ID = entity.ID,
				CategoryID = entity.CategoryID,
				OriginalUrl = entity.OriginalUrl,
				Description = entity.Description,
				Remarks = entity.Remarks,
				Status = entity.Status,
			};
		}

		public void DeletePostOuterInfo(string id)
		{
			this.PostOuterInfoDao.Delete(c => c.ID == id);
		}

		#endregion
	}
}
